Mantle

头像
bawn
    阅读 5 分钟

    Mantle是什么?

    GitHub上的介绍是:

    Model framework for Cocoa and Cocoa Touch

    这是一个模型框架。那么具体有什么作用?

    回忆一下,在开发过程中有没有经常和后台人员沟通关于模型字段命名的问题,是后台人员遵守你的规则,还是你遵守他的规则,或者说各自用不同的。因为这涉及到序列化和反序列化的问题。当然,如果在字段统一的情况下,只需要一句代码就能完成从字典向模型的转换- (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;,但是在实际开发过程中,几乎很难做到这点。比如 id 在Objective-C中是保留字段。Mantle提供了一个转换方法:根据自定义的属性映射关系进行序列化和反序列化,简单的说就是字段转换。

    如何使用?

    字段转换

    Mantle提供了一个基本类:MTLModel,如果你想使用Mantle的各种功能,那么你所创建的模型必须是这个类的子类。
    举个例子,创建一个Member类

    Member.h

    @interface Member : MTLModel<MTLJSONSerializing>
    @property (nonatomic, retain) NSString *memberID;
    @property (nonatomic, retain) NSString *mobilePhone;
    @property (nonatomic, retain) NSDate   *createDate;
    @property (nonatomic, retain) NSNumber *goldNumber;
    

    Member的父类是 MTLModel 同时还遵守 <MTLJSONSerializing> 协议,查看这个协议你会发现里面有个必须实现的方法:

    + (NSDictionary *)JSONKeyPathsByPropertyKey;
    

    这个方法就是前面提到的用于字段转换的,下面实现这个方法

    Member.m

    + (NSDictionary *)JSONKeyPathsByPropertyKey{
        return @{
                 @"memberID" : @"id",
                 @"mobilePhone" : @"phone",
                 @"createDate" : @"date",
                 @"goldNumber" : @"goldNumber"
                 };
    }
    

    这里的意思是:客户端这边的memberID字段对应服务端返回的数据中id字段。注意:本地字段在前,服务端字段在后。完成这个方法就代表着当进行序列化或者反序列化的时候,就会根据这个属性映射关系来进行。 当然,如果key值相同的话就不需要写对应关系了

    注意:最新的2.0版本中,不能再省略相同的字段。也就是说 + (NSDictionary *)JSONKeyPathsByPropertyKey 方法中返回的字典,@"goldNumber" : @"goldNumber" 必须写。如果不写,就相当于不进行序列化,此字段对应的值将位空。

    最后用一句代码来得到你想要的模型

        NSDictionary *response = @{
                              @"id" : @"1",
                              @"phone" : @"xxxxxxxx",
                              @"date" : @"2014-09-09",
                              @"goldNumber" : @2
                              };
        Member *member = [MTLJSONAdapter modelOfClass:[Member class] fromJSONDictionary:response error:nil];
    

    是的,这样就完成了字段转换,比起写繁杂的if/else来做字段转换,简直方便多了。

    当然,Mantle的功能不止上述的一个。介绍其他功能之前,我们先为Member增加几个字段

    @interface Member : MTLModel<MTLJSONSerializing>
    @property (nonatomic, retain) NSString   *memberID;
    @property (nonatomic, retain) NSString   *mobilePhone;
    @property (nonatomic, retain) NSDate     *createDate;
    @property (nonatomic, retain) NSNumber   *goldNumber;
    @property (nonatomic, assign) NSUInteger age;
    @property (nonatomic, assign) BOOL       isVip;
    @property (nonatomic, retain) NSURL      *url;
    

    拿createDate字段来说,如果想在模型中直接得到NSDate类,就必须进行NSString-->NSDate的类型转换。

    类型转换

    和字段转换的实现方式一样,必须实现<MTLJSONSerializing>中的方法:

    + (NSValueTransformer *)JSONTransformerForKey:(NSString *)key;
    

    具体实现

    + (NSValueTransformer *)JSONTransformerForKey:(NSString *)key{
        if ([key isEqualToString:@"createDate"]) {
            return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *string, BOOL *success, NSError *__autoreleasing *error) {
                return [self.dateFormatter dateFromString:string];
            } reverseBlock:^id(NSDate *date, BOOL *success, NSError *__autoreleasing *error) {
                 return [self.dateFormatter stringFromDate:date];
            }];
        }
        else{
            return nil;
        }
    }
    + (NSDateFormatter *)dateFormatter {
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
        dateFormatter.dateFormat = @"yyyy-MM-dd";
        return dateFormatter;
    }
    

    API:

    + (instancetype)transformerUsingForwardBlock:(MTLValueTransformerBlock)forwardTransformation reverseBlock:(MTLValueTransformerBlock)reverseTransformation;
    

    第一个Block的返回的值是字典-->模型转换的结果,第二Block的返回值是模型-->字典转换的结果。当然如果我们只需要序列化,那么就实现单向转换即可,使用下列API:

    + (instancetype)transformerUsingForwardBlock:(MTLValueTransformerBlock)transformation;
    

    Mantle其实还提供了另一种实现方式,同样的实现上述功能

    + (NSValueTransformer *)createDateJSONTransformer{
        return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *string, BOOL *success, NSError *__autoreleasing *error) {
                return [self.dateFormatter dateFromString:string];
            } reverseBlock:^id(NSDate *date, BOOL *success, NSError *__autoreleasing *error) {
                 return [self.dateFormatter stringFromDate:date];
            }];
    }
    

    方法命名规则是:+<key>JSONTransformer,另外对于BOOL和NSURL类型的有更快捷的方法:

    + (NSValueTransformer *)urlJSONTransformer{
        return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
    }
    + (NSValueTransformer *)isVipJSONTransformer{
        return [NSValueTransformer valueTransformerForName:MTLBooleanValueTransformerName];
    }
    

    最后关于age字段的转换

    + (NSValueTransformer *)ageJSONTransformer{
        return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *string, BOOL *success, NSError *__autoreleasing *error) {
            return @([string integerValue]);
        } reverseBlock:^id(NSNumber *number, BOOL *success, NSError *__autoreleasing *error) {
            return [number stringValue];
        }];
    }
    

    不要问我为什么返回的是NSNumber类型也可以

    空对象处理

    先来看一段代码

        NSDictionary *response = @{@"id" : @"1",
                              @"phone" : @"xxxxxx",
                              @"date" : @"2014-09-09",
                              @"goldNumber" : @2,
                              @"age" : @"18",
                              @"url" : @"http://bawn.github.io/",
                              @"isVip" : NSNull.null
                              };
        Member *member = [MTLJSONAdapter modelOfClass:[Member class] fromJSONDictionary:response error:nil];
    

    这里模拟的是服务端返回空的isVip字段对应的值,运行的结果当然是crash,Mantle也为这种情况提供了解决办法,实现Mantle内部会把值转换为nil,然后需要我们去实现 - (void)setNilValueForKey:(NSString *)key; 方法即可

    Member.m

    - (void)setNilValueForKey:(NSString *)key{
        if ([key isEqualToString:@"isVip"]) {
            self.isVip = 0;
        }
        else{
            [super setNilValueForKey:key];
        }
    }
    

    这种问题其实只针对于非指针类型,像float,bool,double。

    Core Data相关

    Mantle还提供了一个专门操作Core Data的类 MTLManagedObjectAdapter ,其中包括有些非常有用的方法,比如:唯一性检查、实体属性转换等。下一篇博文我将着重讲述 MagicalRecord 配合Mantle的使用。

    最后

    Mantle当然还有其他一些功能
    * 归档:已实现了NSCoding协议
    * 比较:- (BOOL)isEqual:(id)object;,默认实现-hash

    Demo地址:MagicalRecord-Mantle


    bawn
    515 声望44 粉丝

    欢迎技术交流